import { Signaling, WebSocketSignaling } from "./signaling.js";
import Peer from "./peer.js";
import * as Logger from "./logger.js";


// enum type of event sending from Unity
var UnityEventType = {
    SWITCH_VIDEO: 0
};

function uuid4() {
    var temp_url = URL.createObjectURL(new Blob());
    var uuid = temp_url.toString();
    URL.revokeObjectURL(temp_url);
    return uuid.split(/[:/]/g).pop().toLowerCase(); // remove prefixes
}

export class VideoPlayer {
    constructor(elements) {
        const _this = this;
        this.pc = null;
        this.channel = null;
        this.connectionId = null;

        // main video
        this.localStream = new MediaStream();
        this.video = elements[0];
        this.video.playsInline = true;
        this.video.addEventListener('loadedmetadata', function () {
            _this.video.play();
            _this.resizeVideo();
        }, true);

        // secondly video
        this.localStream2 = new MediaStream();
        this.videoThumb = elements[1];
        this.videoThumb.playsInline = true;
        this.videoThumb.addEventListener('loadedmetadata', function () {
            _this.videoThumb.play();
        }, true);

        this.videoTrackList = [];
        this.videoTrackIndex = 0;
        this.maxVideoTrackLength = 2;

        this.ondisconnect = function () { };
    }

    async setupConnection(useWebSocket) {
        const _this = this;
        // close current RTCPeerConnection
        if (this.pc) {
            Logger.log('Close current PeerConnection');
            this.pc.close();
            this.pc = null;
        }

        if (useWebSocket) {
            this.signaling = new WebSocketSignaling();
            console.log('开启websocket连接！');
        } else {
            this.signaling = new Signaling();
            console.log('开启信令连接！');
        }

        this.connectionId = uuid4();

        // Create peerConnection with proxy server and set up handlers
        this.pc = new Peer(this.connectionId, true);
        this.pc.addEventListener('disconnect', () => {
            _this.ondisconnect();
        });
        this.pc.addEventListener('trackevent', (e) => {
            const data = e.detail;
            if (data.track.kind == 'video') {
                _this.videoTrackList.push(data.track);
            }
            if (data.track.kind == 'audio') {
                _this.localStream.addTrack(data.track);
            }
            if (_this.videoTrackList.length == _this.maxVideoTrackLength) {
                _this.switchVideo(_this.videoTrackIndex);
            }
        });
        this.pc.addEventListener('sendoffer', (e) => {
            const offer = e.detail;
            _this.signaling.sendOffer(offer.connectionId, offer.sdp);
        });
        this.pc.addEventListener('sendanswer', (e) => {
            const answer = e.detail;
            _this.signaling.sendAnswer(answer.connectionId, answer.sdp);
        });
        this.pc.addEventListener('sendcandidate', (e) => {
            const candidate = e.detail;
            _this.signaling.sendCandidate(candidate.connectionId, candidate.candidate, candidate.sdpMid, candidate.sdpMLineIndex);
        });

        this.signaling.addEventListener('disconnect', async (e) => {
            const data = e.detail;
            if (_this.pc != null && _this.pc.connectionId == data.connectionId) {
                _this.ondisconnect();
            }
        });
        this.signaling.addEventListener('offer', async (e) => {
            const offer = e.detail;
            const desc = new RTCSessionDescription({ sdp: offer.sdp, type: "offer" });
            if (_this.pc != null) {
                await _this.pc.onGotDescription(offer.connectionId, desc);
            }
        });
        this.signaling.addEventListener('answer', async (e) => {
            const answer = e.detail;
            const desc = new RTCSessionDescription({ sdp: answer.sdp, type: "answer" });
            if (_this.pc != null) {
                await _this.pc.onGotDescription(answer.connectionId, desc);
            }
        });
        this.signaling.addEventListener('candidate', async (e) => {
            const candidate = e.detail;
            const iceCandidate = new RTCIceCandidate({ candidate: candidate.candidate, sdpMid: candidate.sdpMid, sdpMLineIndex: candidate.sdpMLineIndex });
            if (_this.pc != null) {
                await _this.pc.onGotCandidate(candidate.connectionId, iceCandidate);
            }
        });

        // setup signaling
        await this.signaling.start();

        // Create data channel with proxy server and set up handlers
        this.channel = this.pc.createDataChannel(this.connectionId, 'data');
        this.channel.onopen = function () {
            Logger.log('Datachannel connected.');
        };
        this.channel.onerror = function (e) {
            Logger.log("The error " + e.error.message + " occurred\n while handling data with proxy server.");
        };
        this.channel.onclose = function () {
            Logger.log('Datachannel disconnected.');
        };
        this.channel.onmessage = async (msg) => {
            // receive message from unity and operate message
            let data;
            // receive message data type is blob only on Firefox
            if (navigator.userAgent.indexOf('Firefox') != -1) {
                data = await msg.data.arrayBuffer();
            } else {
                data = msg.data;
            }
            const bytes = new Uint8Array(data);
            _this.videoTrackIndex = bytes[1];
            switch (bytes[0]) {
                case UnityEventType.SWITCH_VIDEO:
                    _this.switchVideo(_this.videoTrackIndex);
                    break;
            }
        };
    }

    resizeVideo() {
        const clientRect = this.video.getBoundingClientRect();
        const videoRatio = this.videoWidth / this.videoHeight;
        const clientRatio = clientRect.width / clientRect.height;

        this._videoScale = videoRatio > clientRatio ? clientRect.width / this.videoWidth : clientRect.height / this.videoHeight;
        const videoOffsetX = videoRatio > clientRatio ? 0 : (clientRect.width - this.videoWidth * this._videoScale) * 0.5;
        const videoOffsetY = videoRatio > clientRatio ? (clientRect.height - this.videoHeight * this._videoScale) * 0.5 : 0;
        this._videoOriginX = clientRect.left + videoOffsetX;
        this._videoOriginY = clientRect.top + videoOffsetY;
    }

    // switch streaming destination main video and secondly video
    switchVideo(indexVideoTrack) {
        this.video.srcObject = this.localStream;
        this.videoThumb.srcObject = this.localStream2;

        if (indexVideoTrack == 0) {
            this.replaceTrack(this.localStream, this.videoTrackList[0]);
            this.replaceTrack(this.localStream2, this.videoTrackList[1]);
        }
        else {
            this.replaceTrack(this.localStream, this.videoTrackList[1]);
            this.replaceTrack(this.localStream2, this.videoTrackList[0]);
        }
    }

    // replace video track related the MediaStream
    replaceTrack(stream, newTrack) {
        const tracks = stream.getVideoTracks();
        for (const track of tracks) {
            if (track.kind == 'video') {
                stream.removeTrack(track);
            }
        }
        stream.addTrack(newTrack);
    }

    get videoWidth() {
        return this.video.videoWidth;
    }

    get videoHeight() {
        return this.video.videoHeight;
    }

    get videoOriginX() {
        return this._videoOriginX;
    }

    get videoOriginY() {
        return this._videoOriginY;
    }

    get videoScale() {
        return this._videoScale;
    }

    sendMsg(msg) {
        if (this.channel == null) {
            return;
        }
        switch (this.channel.readyState) {
            case 'connecting':
                Logger.log('Connection not ready');
                break;
            case 'open':
                this.channel.send(msg);
                break;
            case 'closing':
                Logger.log('Attempt to sendMsg message while closing');
                break;
            case 'closed':
                Logger.log('Attempt to sendMsg message while connection closed.');
                break;
        }
    }

    async stop() {
        if (this.signaling) {
            await this.signaling.stop();
            this.signaling = null;
        }

        if (this.pc) {
            this.pc.close();
            this.pc = null;
        }
    }
}
